﻿using Microsoft.EntityFrameworkCore;
using Nantianmen.Domain.Models;
using Nantianmen.EntityFrameworkCore.ValueConveters;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace Nantianmen.EntityFrameworkCore
{
    public class NantianmenDbContext : DbContext
    {
        public NantianmenDbContext(DbContextOptions options) : base(options)
        {
        }

        public DbSet<Destination> Destinations => Set<Destination>();
        public DbSet<Cluster> Clusters => Set<Cluster>();
        public DbSet<ClusterDestination> ClusterDestinations => Set<ClusterDestination>();
        public DbSet<Route> Routes => Set<Route>();

        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = default)
        {
            SetAuditTime();

            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }

        private void SetAuditTime()
        {
            foreach (var entry in ChangeTracker.Entries())
            {
                switch (entry.State)
                {
                    case EntityState.Detached:
                        break;
                    case EntityState.Unchanged:
                        break;
                    case EntityState.Deleted:
                        break;
                    case EntityState.Modified:
                        if (entry.Entity is AuditTime modified)
                        {
                            modified.UpdatedAt = DateTime.Now;
                        }
                        break;
                    case EntityState.Added:
                        if (entry.Entity is AuditTime added)
                        {
                            added.CreatedAt = DateTime.Now;
                            added.UpdatedAt = DateTime.Now;
                        }
                        break;
                    default:
                        break;
                }
            }
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            var cluster = modelBuilder.Entity<Cluster>();
            cluster.OwnsOne(entity => entity.HealthCheck,
                         ownedNavigationBuilder =>
                         {
                             ownedNavigationBuilder.ToJson();
                             ownedNavigationBuilder.OwnsOne(healthConfig => healthConfig.Passive);
                             ownedNavigationBuilder.OwnsOne(healthConfig => healthConfig.Active);
                         });
            cluster.OwnsOne(entity => entity.SessionAffinity,
                     ownedNavigationBuilder =>
                     {
                         ownedNavigationBuilder.ToJson();
                         ownedNavigationBuilder.OwnsOne(sessionAffinity => sessionAffinity.Cookie);
                     });
            cluster.OwnsOne(entity => entity.HttpClient,
                     ownedNavigationBuilder =>
                     {
                         ownedNavigationBuilder.ToJson();
                         ownedNavigationBuilder.OwnsOne(httpClient => httpClient.WebProxy);
                     });
            cluster.OwnsOne(entity => entity.HttpRequest,
                     ownedNavigationBuilder =>
                     {
                         ownedNavigationBuilder.ToJson();
                         ownedNavigationBuilder.Property(x => x.Version)
                                               .HasConversion<VersionValueConverter>();
                     });
            cluster.Property(config => config.Metadata)
                                .HasConversion<MetadataValueConverter>();

            modelBuilder.Entity<Destination>()
                .HasIndex(x => x.Address);

            modelBuilder.Entity<ClusterDestination>()
                .HasKey(x => new
                {
                    x.ClusterId,
                    x.DestinationId
                });

            var route = modelBuilder.Entity<Route>();
            route.OwnsOne(e => e.Match, match =>
            {
                match.ToJson();
                match.OwnsMany(x => x.Headers, headers =>
                {
                    headers.ToJson();
                    headers.Property(he => he.Values).HasConversion<ListStringValueConverter>();
                    headers.Property(he => he.Mode).HasConversion<HeaderMatchModeValueConverter>();
                });
                match.OwnsMany(x => x.QueryParameters, queryParameters =>
                {
                    queryParameters.ToJson();
                    queryParameters.Property(qpe => qpe.Values).HasConversion<ListStringValueConverter>();
                    queryParameters.Property(qpe => qpe.Mode).HasConversion<QueryParameterMatchModeValueConverter>();
                });
                match.Property(x => x.Methods).HasConversion<ListStringValueConverter>();
                match.Property(x => x.Hosts).HasConversion<ListStringValueConverter>();
            });
            route.Property(e => e.Metadata).HasConversion<MetadataValueConverter>();
            route.Property(e => e.Transforms).HasConversion<TransformsValueConverter>();
        }

        protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
        {
            base.ConfigureConventions(configurationBuilder);

            configurationBuilder.Properties<DateTime>().HaveConversion<DateTimeUtcConverter>();
            configurationBuilder.Properties<DateTimeOffset>().HaveConversion<DateTimeOffsetUtcConverter>();
        }
    }
}
